package sketch.generator;

import java.util.LinkedList;
import java.util.List;

import randoop.StatementKind;
import sketch.ExecutableSequenceContent;
import sketch.MethodExecOutcome;
import sketch.SMethod;
import sketch.SketchSequence;
import sketch.generator.examples.SequentialValue;
import sketch.util.Checker;
import sketch.util.Globals;
import sketch.util.RandoopUtil;
import sketch.util.SketchUtil;

public class ExhaustiveSequenceGenerator<T extends SketchSequence, ValueType> extends SequenceGenerator<T> {

	private final ExhaustiveValueGenerator<ValueType> exhaustiveGenerator;
	
	public final ExhaustiveTypedValueGenerator<ValueType> typedValueGenerator;
	
	private SketchSequence currentGeneratedSequence = null;
	
	private final String methodName;
	
	private final List<?> valuesForExhaustion;
	
	private final StatementKind s;
	
	//XXX i should add some length restriction
	public ExhaustiveSequenceGenerator(Object receiver, SMethod smethod, ValueGenerator<ValueType> generator) {
		super(receiver, smethod, generator);
		//XXX we currently only support exhaustively enumerate one single method
		//with its argument varied
		Checker.checkTrue(smethod.getMethodNum() == 1, "We currently only support" +
				" exhaustive enumerate all possible inputs for one method.");
		
		this.methodName = smethod.methodNames.get(0);
		
		List<ValueType> values = new LinkedList<ValueType>();
		while(generator.hasNext()) {
			values.add(generator.next());
		}
		this.valuesForExhaustion = values;
		//xxx
		this.exhaustiveGenerator = new ExhaustiveValueGenerator<ValueType>(values);
		generator.reset();
		
		//initialize the typedValueGenerator
		this.typedValueGenerator = new ExhaustiveTypedValueGenerator(smethod.methods.get(0).getParameterTypes(), values);
		
		//get the statement
		this.s = GenTests.lookupForStatement(this.smethod.sclazz.clazz, this.methodName);
		Checker.checkNull(this.s, "StatementKind for method: " + this.methodName + " is null!");
	}
	
	@Override
	public void reset() {
		super.reset();
		this.exhaustiveGenerator.reset();
		this.currentGeneratedSequence = null;
		this.typedValueGenerator.reset();
	}
	
	
	@Override
	protected SketchSequence generateNext() {
		//System.out.println("-- call generate next() in exhaustive sequence");
		if(!hasNext()) {
			throw new IterationException("There is no more next() element to generate");
		}
		this.currentGeneratedSequence = this.composeSequence();
		
		return this.currentGeneratedSequence;
	}

	@Override
	public boolean hasNext() {
		if(RandoopUtil.getParamNum(this.s) != 1) {
			//throw new UnsupportedOperationException("no support for method: " + s);
			return this.typedValueGenerator.hasNext();
		} else {
		    return this.exhaustiveGenerator.hasNextValues();
		}
	}

	@Override
	public ExecutableSequenceContent[] executables() {
		ExecutableSequenceContent[] contents = null;
		if (RandoopUtil.getParamNum(this.s) == 1) {
			List<ValueType> values = this.exhaustiveGenerator.currentValue();
			contents = new ExecutableSequenceContent[values.size()];
			int index = 0;
			for (ValueType value : values) {
				StatementKind s = GenTests.lookupForStatement(
						this.smethod.sclazz.clazz, this.methodName);
				ExecutableSequenceContent content = new ExecutableSequenceContent(
						super.getReceiver(), s, value);
				contents[index] = content;
				index++;
			}
		} else {
			SequentialValue<ValueType> sequentialValue = this.typedValueGenerator.current();
			List<ValueType> values = sequentialValue.getValues();
			Object[] args = new Object[values.size()];
			int index = 0;
			for(ValueType value : values) {
				args[index] = value;
				index++;
			}
			contents = new ExecutableSequenceContent[1];
			contents[0] = new ExecutableSequenceContent(super.getReceiver(), s, args);
		}
		Checker.checkNull(contents, "should not be null");
		
		return contents;
	}

	@Override
	protected SketchSequence composeSequence() {
		if(!this.exhaustiveGenerator.hasNextValues()) {
			throw new IterationException("No values for exhaustive generation.");
		}
		SketchSequence sequence = new SketchSequence();
		if (RandoopUtil.getParamNum(this.s) == 1) {
			List<ValueType> values = this.exhaustiveGenerator.nextValue();
			for (ValueType value : values) {
				// get the statement

				// mocking
				String method = SketchUtil.removeMethodParam(this.methodName);
				sequence.addText(SketchSequence.RECEIVER// SketchUtil.createVarName(super.getReceiver().getClass())
						+ "." + method + "(" + value + ");");
			}
		} else {
			SequentialValue<ValueType> sequentialValue = this.typedValueGenerator.next();
			List<ValueType> values = sequentialValue.getValues();
			String method = SketchUtil.removeMethodParam(this.methodName);
			StringBuilder sb = new StringBuilder();
			sb.append(SketchSequence.RECEIVER);
			sb.append(".");
			sb.append(method);
			sb.append("(");
			for(int i = 0; i < values.size(); i++) {
				ValueType value = values.get(i);
				if(i!=0) {
					sb.append(",");
				}
				sb.append(value);
			}
			sb.append(");");
			
			sequence.addText(sb.toString());
		}
		
		return sequence;
	}

	@Override
	public SketchSequence getGeneratedSequence() {
		Checker.checkNull(this.currentGeneratedSequence, "There is no currently generated sequence!");
		return this.currentGeneratedSequence;
	}

}
